home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
The Fatted Calf
/
The Fatted Calf.iso
/
Modules
/
BackSpaceModules
/
Source
/
MazeView
/
MazeView.m
< prev
next >
Wrap
Text File
|
1994-01-12
|
19KB
|
721 lines
// MazeView by David Bau
// Copyright 1994 by David Bau. All rights reserved.
//
// I feel silly even saying this, but please don't charge for this
// module or any product containing a portion or modification of it,
// and when distributing, please give credit where credit is due.
//
// If you add anything to this module or derive anything interesting
// from it, please let me know!
//
// I'll probably be bau@cs.cornell.edu for a while.
//
// David Bau 11/13/93
// 777 South Avenue, Weston, MA 02193
#import <appkit/appkit.h>
#import <defaults/defaults.h>
#import <libc.h>
#import <time.h>
#import <dpsclient/wraps.h>
#import "MazeView.h"
#import "RandomIntPicker.h"
#import "Thinker.h"
static void ColorToString(NXColor color, char *str);
static NXColor ColorFromString(const char *str);
static NXColor RandomColor();
/* the kind of Maze view that appears in the control panel */
@implementation StaticMazeView
- initMaze
{
/* frame the field 2 cells bigger than the view */
ncols=MIN((((int)bounds.size.width-wallSize)/cellSize+2),MAXCOLS);
nrows=MIN((((int)bounds.size.height-wallSize)/cellSize+2),MAXROWS);
xoffset=((int)bounds.size.width-(ncols-2)*cellSize-wallSize)/2;
yoffset=((int)bounds.size.height-(nrows-2)*cellSize-wallSize)/2;
/* allocate grid */
if (Grid) NXZoneFree([self zone],Grid);
NX_ZONEMALLOC([self zone],Grid,mazecell,nrows*ncols);
[self computeMaze];
/* choose a random starting place and direction */
icur=(random()%(ncols-2)+1)+(random()%(nrows-2)+1)*ncols;
dcur=random()%4;
Grid[icur].bdir=4;
ifirst=icur;
Grid[icur].next=(-1);
/* set the current colors */
curWallColor=wallColor;
curPathColor=pathColor;
/* clear drawing buffers */
wallRectListSize=0;
pathRectListSize=0;
/* quickly do half-of-a-maze-exploration */
countDown=((nrows-2)*(ncols-2)*2-2)/2;
while (countDown--) {
int inew;
inew=icur;
switch (dcur) {
case 0:
if (!Grid[icur].eastwall) {dcur=0; inew=icur+1; break;}
case 1:
if (!Grid[icur].northwall) {dcur=1; inew=icur+ncols; break;}
case 2:
if (!Grid[icur].westwall) {dcur=2; inew=icur-1; break;}
case 3:
if (!Grid[icur].southwall) {dcur=3; inew=icur-ncols; break;}
if (!Grid[icur].eastwall) {dcur=0; inew=icur+1; break;}
if (!Grid[icur].northwall) {dcur=1; inew=icur+ncols; break;}
if (!Grid[icur].westwall) {dcur=2; inew=icur-1; break;}
}
if (Grid[icur].bdir==dcur) {
Grid[icur].bdir=(-1);
} else {
Grid[inew].next=ifirst;
ifirst=inew;
Grid[inew].bdir=dcur^2;
}
icur=inew;
dcur=(dcur+3)%4;
}
return self;
}
/* main view can tell panel view what colors to use */
- setWallColor:(NXColor)wc pathColor:(NXColor)pc
{
curWallColor=wallColor=wc;
curPathColor=pathColor=pc;
return self;
}
@end
@implementation MazeView
- initMaze
{
/* frame the field 2 cells bigger than the view */
ncols=MIN((((int)bounds.size.width-wallSize)/cellSize+2),MAXCOLS);
nrows=MIN((((int)bounds.size.height-wallSize)/cellSize+2),MAXROWS);
xoffset=random()%((int)bounds.size.width-(ncols-2)*cellSize-wallSize+1);
yoffset=random()%((int)bounds.size.height-(nrows-2)*cellSize-wallSize+1);
/* allocate grid */
if (Grid) NXZoneFree([self zone],Grid);
NX_ZONEMALLOC([self zone],Grid,mazecell,nrows*ncols);
[self computeMaze];
countDown=(-1);
ifirst=(-1);
return self;
}
- computeMaze
{
RandomIntPicker *picker;
/* clear the "last" fields */
[self clearMaze];
/* compute the maze */
icur=(random()%(ncols-2)+1)+(random()%(nrows-2)+1)*ncols;
Grid[icur].bdir=4;
Grid[icur].eastwall=Grid[icur].northwall=
Grid[icur].westwall=Grid[icur].southwall=1;
picker=[[RandomIntPicker alloc] init];
while (1) {
[picker reset];
if (Grid[icur+1].bdir<0) [picker insert:0];
if (Grid[icur+ncols].bdir<0) [picker insert:1];
if (Grid[icur-1].bdir<0) [picker insert:2];
if (Grid[icur-ncols].bdir<0) [picker insert:3];
if ([picker count]) {
switch ([picker choice]) {
case 0: icur=icur+1; break;
case 1: icur=icur+ncols; break;
case 2: icur=icur-1; break;
case 3: icur=icur-ncols; break;
}
Grid[icur].bdir=([picker choice]+2)%4;
Grid[icur].eastwall=Grid[icur].northwall=
Grid[icur].westwall=Grid[icur].southwall=1;
} else {
switch (Grid[icur].bdir) {
case 0: Grid[icur].eastwall=0; icur=icur+1;
Grid[icur].westwall=0; break;
case 1: Grid[icur].northwall=0; icur=icur+ncols;
Grid[icur].southwall=0; break;
case 2: Grid[icur].westwall=0; icur=icur-1;
Grid[icur].eastwall=0; break;
case 3: Grid[icur].southwall=0; icur=icur-ncols;
Grid[icur].northwall=0; break;
case 4: goto finish;
}
}
}
finish:
[picker free];
/* walls are set; clear the "last" fields again */
[self clearMaze];
return self;
}
- firstStep
{
/* choose a random starting place and direction */
icur=(random()%(ncols-2)+1)+(random()%(nrows-2)+1)*ncols;
dcur=random()%4;
Grid[icur].bdir=4;
ifirst=icur;
Grid[icur].next=(-1);
/* set the current colors */
if (randomColor) {
curWallColor=RandomColor();
curPathColor=RandomColor();
} else {
curWallColor=wallColor;
curPathColor=pathColor;
}
if (sharedInspectorPanel) {
if (panelMazeView) {
[panelMazeView setWallColor:curWallColor pathColor:curPathColor];
}
[sharedInspectorPanel display];
}
/* clear drawing buffers */
wallRectListSize=0;
pathRectListSize=0;
/* clear drawing etc */
[self lockFocus];
PSsetgray(NX_BLACK);
NXRectFill(&bounds);
[self drawForward:icur];
[self flushDrawing];
[self unlockFocus];
/* start countDown */
countDown=(nrows-2)*(ncols-2)*2-2;
return self;
}
/* here's where the maze solving and drawing is done */
- oneStep
{
int inew;
BStimeval curtime;
/* enforce the delay between frames */
curtime=currentTimeInMs();
if (curtime-lasttime<delay) {
if (curtime-lasttime-delay>30) return self;
usleep((delay-(curtime-lasttime))*1000);
curtime=currentTimeInMs();
}
lasttime=curtime;
/* check if finished */
if (countDown<=0) {
/* do the following only when recycling, not when running 1st time */
if (!countDown) {
[self drawBack:icur];
[self flushDrawing];
if (panelMazeView) {
[panelMazeView initMaze];
}
[self initMaze];
}
[self firstStep];
return self;
}
countDown--;
/* follow-the-right-wall algorithm... switch fallthrough intentional */
inew=icur;
switch (dcur) {
case 0:
if (!Grid[icur].eastwall) {dcur=0; inew=icur+1; break;}
case 1:
if (!Grid[icur].northwall) {dcur=1; inew=icur+ncols; break;}
case 2:
if (!Grid[icur].westwall) {dcur=2; inew=icur-1; break;}
case 3:
if (!Grid[icur].southwall) {dcur=3; inew=icur-ncols; break;}
if (!Grid[icur].eastwall) {dcur=0; inew=icur+1; break;}
if (!Grid[icur].northwall) {dcur=1; inew=icur+ncols; break;}
if (!Grid[icur].westwall) {dcur=2; inew=icur-1; break;}
}
if (Grid[icur].bdir==dcur) {
[self drawBack:icur];
Grid[icur].bdir=(-1);
} else {
Grid[inew].next=ifirst;
ifirst=inew;
Grid[inew].bdir=dcur^2;
[self drawForward:inew];
}
[self flushDrawing];
icur=inew;
dcur=(dcur+3)%4;
return self;
}
- drawForward:(int) index
{
int x,y;
x=(index%ncols-1)*cellSize+xoffset;
y=(index/ncols-1)*cellSize+yoffset;
switch (Grid[index].bdir) {
case 0:
[self addPathRectOrigin: x+wallSize+gapSize:y+wallSize+gapSize
size: cellSize:pathSize]; break;
case 1:
[self addPathRectOrigin: x+wallSize+gapSize:y+wallSize+gapSize
size: pathSize:cellSize]; break;
case 2:
[self addPathRectOrigin: x-gapSize:y+wallSize+gapSize
size: cellSize:pathSize]; break;
case 3:
[self addPathRectOrigin: x+wallSize+gapSize:y-gapSize
size: pathSize:cellSize]; break;
case 4:
[self addPathRectOrigin:x+wallSize+gapSize:y+wallSize+gapSize
size: pathSize:pathSize]; break;
}
if (Grid[index].eastwall && Grid[index+1].next==(-2)) {
[self addWallRectOrigin:x+cellSize:y size:wallSize:cellSize+wallSize];
}
if (Grid[index].northwall && Grid[index+ncols].next==(-2)) {
[self addWallRectOrigin:x:y+cellSize size:cellSize+wallSize:wallSize];
}
if (Grid[index].westwall) {
[self addWallRectOrigin:x:y size:wallSize:cellSize+wallSize];
}
if (Grid[index].southwall) {
[self addWallRectOrigin:x:y size:cellSize+wallSize:wallSize];
}
return self;
}
- drawBack:(int) index
{
int x,y;
x=(index%ncols-1)*cellSize+xoffset;
y=(index/ncols-1)*cellSize+yoffset;
[self addEraseRectOrigin:x+wallSize+gapSize:y+wallSize+gapSize
size:pathSize:pathSize];
switch (Grid[index].bdir) {
case 0:
[self addEraseRectOrigin: x+wallSize+gapSize:y+wallSize+gapSize
size: cellSize:pathSize]; break;
case 1:
[self addEraseRectOrigin: x+wallSize+gapSize:y+wallSize+gapSize
size: pathSize:cellSize]; break;
case 2:
[self addEraseRectOrigin: x-gapSize:y+wallSize+gapSize
size: cellSize:pathSize]; break;
case 3:
[self addEraseRectOrigin: x+wallSize+gapSize:y-gapSize
size: pathSize:cellSize]; break;
case 4:
[self addEraseRectOrigin:x+wallSize+gapSize:y+wallSize+gapSize
size: pathSize:pathSize]; break;
}
return self;
}
- addWallRectOrigin:(int) x :(int) y size:(int)w :(int) h
{
wallRectList[wallRectListSize].origin.x=x;
wallRectList[wallRectListSize].origin.y=y;
wallRectList[wallRectListSize].size.height=h;
wallRectList[wallRectListSize].size.width=w;
if (++wallRectListSize>=RECTBUFSIZE) {
[self flushWallRects];
}
return self;
}
- flushWallRects
{
if (wallRectListSize) {
NXSetColor(curWallColor);
NXRectFillList(wallRectList, wallRectListSize);
wallRectListSize=0;
}
return self;
}
- addPathRectOrigin:(int) x :(int) y size:(int)w :(int) h
{
pathRectList[pathRectListSize].origin.x=x;
pathRectList[pathRectListSize].origin.y=y;
pathRectList[pathRectListSize].size.height=h;
pathRectList[pathRectListSize].size.width=w;
if (++pathRectListSize>=RECTBUFSIZE) {
[self flushPathRects];
}
return self;
}
- flushPathRects
{
if (pathRectListSize) {
NXSetColor(curPathColor);
NXRectFillList(pathRectList, pathRectListSize);
pathRectListSize=0;
}
return self;
}
- addEraseRectOrigin:(int) x :(int) y size:(int)w :(int) h
{
eraseRectList[eraseRectListSize].origin.x=x;
eraseRectList[eraseRectListSize].origin.y=y;
eraseRectList[eraseRectListSize].size.height=h;
eraseRectList[eraseRectListSize].size.width=w;
if (++eraseRectListSize>=RECTBUFSIZE) {
[self flushEraseRects];
}
return self;
}
- flushEraseRects
{
if (eraseRectListSize) {
PSsetgray(NX_BLACK);
NXRectFillList(eraseRectList, eraseRectListSize);
eraseRectListSize=0;
}
return self;
}
- flushDrawing
{
[self flushWallRects];
[self flushPathRects];
[self flushEraseRects];
return self;
}
- drawSelf:(const NXRect *)rects :(int)rectCount
{
int i;
PSsetgray(NX_BLACK);
if (rectCount>1) {
while(--rectCount) {
NXRectFill(rects+rectCount);
}
} else {
NXRectFill(rects);
}
for (i=ifirst; i>=0; i=Grid[i].next) {
[self drawForward:i];
}
[self flushDrawing];
return self;
}
- (const char *) windowTitle
{ return "Maze";
}
- initFrame:(const NXRect *)frameRect
{
[super initFrame:frameRect];
Grid=NULL;
[self getMazeDefaults];
srandom(time(NULL));
[self initMaze];
return self;
}
- free
{
if (Grid) NXZoneFree([self zone], Grid);
Grid=NULL;
return [super free];
}
static void ColorToString(NXColor color, char *str)
{
sprintf(str,"%f %f %f",NXRedComponent(color),
NXGreenComponent(color),NXBlueComponent(color));
}
static NXColor ColorFromString(const char *str)
{
NXColor color;
float r,g,b;
sscanf(str,"%f %f %f",&r,&g,&b);
r=(r<0.0 ? 0.0 : (r>1.0 ? 1.0 : r));
g=(g<0.0 ? 0.0 : (g>1.0 ? 1.0 : g));
b=(b<0.0 ? 0.0 : (b>1.0 ? 1.0 : b));
color=NXConvertRGBToColor(r,g,b);
return color;
}
static NXColor RandomColor()
{
NXColor color;
float r,g,b;
r=((random()%65536)/65536.0)/2;
g=((random()%65536)/65536.0)/2;
b=((random()%65536)/65536.0)/2;
color=NXConvertRGBToColor(r,g,b);
return color;
}
- getMazeDefaults
{
char c;
static NXDefaultsVector MazeDefaults = {
{"MazeFrameDelay", "16"},
{"MazeRandomColor", "No"},
{"MazeWallColor", "0.000000 0.000000 0.666662"},
{"MazePathColor","0.666662 0.666662 0.000000"},
{"MazeWallSize", "2"},
{"MazePathSize", "4"},
{"MazeGapSize", "1"},
{NULL}
};
NXRegisterDefaults("BackSpace",MazeDefaults);
sscanf(NXGetDefaultValue("BackSpace","MazeFrameDelay"),"%d",&delay);
if (delay<0) delay=0;
if (delay>MAXDELAY) delay=MAXDELAY;
if ((c=*NXGetDefaultValue("BackSpace","MazeRandomColor"))=='y' || c=='Y') {
randomColor=YES;
} else {
randomColor=NO;
}
wallColor=
ColorFromString(NXGetDefaultValue("BackSpace","MazeWallColor"));
pathColor=
ColorFromString(NXGetDefaultValue("BackSpace","MazePathColor"));
sscanf(NXGetDefaultValue("BackSpace","MazeWallSize"),"%d",&wallSize);
sscanf(NXGetDefaultValue("BackSpace","MazePathSize"),"%d",&pathSize);
sscanf(NXGetDefaultValue("BackSpace","MazeGapSize"),"%d",&gapSize);
cellSize=pathSize+wallSize+2*gapSize;
if (cellSize<MINCELLSIZE) {
pathSize+=MINCELLSIZE-cellSize;
cellSize=MINCELLSIZE;
}
return self;
}
- sizeTo:(NXCoord)width :(NXCoord)height
{
[super sizeTo:width :height];
[self initMaze];
return self;
}
- clearMaze
{
int x,y;
icur = -1;
/* clear the field */
for (y=0; y<nrows; y++) {
for (x=0; x<ncols; x++) {
if (x==0 || y==0 || x==ncols-1 || y==nrows-1) {
Grid[x+y*ncols].bdir=4;
} else {
Grid[x+y*ncols].bdir=(-1);
}
Grid[x+y*ncols].next=(-2);
}
}
return self;
}
/* quick update called when color has been changed */
- updateViews
{
/* update the panel view, if needed */
if (sharedInspectorPanel) {
if (panelMazeView) {
[panelMazeView setWallColor:curWallColor pathColor:curPathColor];
}
[sharedInspectorPanel display];
}
/* update myself */
[self lockFocus];
[self display];
[self unlockFocus];
return self;
}
- inspector:sender
{
char buf[MAXPATHLEN];
if (!sharedInspectorPanel) {
sprintf(buf,"%s/MazeInspector.nib",[sender moduleDirectory:"Maze"]);
[NXApp loadNibFile:buf owner:self withNames:NO];
/* initialize some of the panel objects... */
if (panelWallColorWell) {
[panelWallColorWell setColor:wallColor];
[panelWallColorWell setEnabled:!randomColor];
}
if (panelPathColorWell) {
[panelPathColorWell setColor:pathColor];
[panelPathColorWell setEnabled:!randomColor];
}
if (randomColorSwitch) [randomColorSwitch setState:randomColor];
if (panelSpeedSlider)
[panelSpeedSlider setFloatValue:(float)(-log(delay/100.0)/log(100))];
}
return sharedInspectorPanel;
}
- inspectorInstalled
{
[self hideCredits:self];
if (sharedInspectorPanel) {
[sharedInspectorPanel display];
}
return self;
}
- doRandomColorSwitch:sender
{
if ([sender state]) {
[panelWallColorWell setEnabled:NO];
[panelPathColorWell setEnabled:NO];
randomColor=YES;
curWallColor=RandomColor();
curPathColor=RandomColor();
NXWriteDefault("BackSpace","MazeRandomColor","Yes");
} else {
[panelWallColorWell setEnabled:YES];
[panelPathColorWell setEnabled:YES];
randomColor=NO;
curWallColor=wallColor;
curPathColor=pathColor;
NXWriteDefault("BackSpace","MazeRandomColor","No");
}
[self updateViews];
return self;
}
- doSpeedSlider:sender
{
char str[80];
delay=100*exp(-[sender floatValue]*log(100));
sprintf(str,"%d",delay);
NXWriteDefault("BackSpace","MazeFrameDelay",str);
return self;
}
- takeWallColorFrom:sender
{
char str[80];
curWallColor=wallColor=[(NXColorWell *)sender color];
[self updateViews];
ColorToString(wallColor,str);
NXWriteDefault("BackSpace","MazeWallColor",str);
return self;
}
- takePathColorFrom:sender
{
char str[80];
curPathColor=pathColor=[(NXColorWell *)sender color];
[self updateViews];
ColorToString(pathColor,str);
NXWriteDefault("BackSpace","MazePathColor",str);
return self;
}
- showCredits:sender
{
if (panelCreditsView && sharedInspectorPanel) {
if ([panelCreditsView isDescendantOf:sharedInspectorPanel]) {
[panelCreditsView removeFromSuperview];
}
[sharedInspectorPanel addSubview:panelCreditsView];
[sharedInspectorPanel display];
}
return self;
}
- hideCredits:sender
{
if (panelCreditsView && sharedInspectorPanel) {
if ([panelCreditsView isDescendantOf:sharedInspectorPanel]) {
[panelCreditsView removeFromSuperview];
}
[sharedInspectorPanel display];
}
return self;
}
/* these methods are needed because we should not rearrange the view */
/* hierarchy while a button is active; we queue the rearranging to be */
/* done later as an event, when nothing is locked on the view */
- doShowCredits:sender
{
[self perform:@selector(showCredits:)
with:self afterDelay:0 cancelPrevious:YES];
return self;
}
- doHideCredits:sender
{
[self perform:@selector(hideCredits:)
with:self afterDelay:0 cancelPrevious:YES];
return self;
}
@end